diff --git a/tools/Makefile b/tools/Makefile
index 61b2048..5b9d96a 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -142,6 +142,8 @@ hostprogs-$(CONFIG_KIRKWOOD) += kwboot
 hostprogs-y += proftool
 hostprogs-$(CONFIG_STATIC_RELA) += relocate-rela
 
+hostprogs-y += boot0img
+
 # We build some files with extra pedantic flags to try to minimize things
 # that won't build on some weird host compiler -- though there are lots of
 # exceptions for files that aren't complaint.
diff --git a/tools/boot0img.c b/tools/boot0img.c
new file mode 100644
index 0000000..8370a7d
--- /dev/null
+++ b/tools/boot0img.c
@@ -0,0 +1,746 @@
+/*
+ * Copyright 2016 Andre Przywara <osp@andrep.de>
+ *
+ * This programme is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This programme is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this programme.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+
+enum header_offsets {				/* in words of 4 bytes */
+	HEADER_JUMP_INS	= 0,
+	HEADER_MAGIC	= 1,
+	HEADER_CHECKSUM	= 3,
+	HEADER_ALIGN	= 4,
+	HEADER_LENGTH	= 5,
+	HEADER_PRIMSIZE	= 6,
+	HEADER_LOADADDR = 11,
+	HEADER_SECS	= 0x500 / 4,
+	HEADER_DTOFFSET = 315,
+};
+
+#define MAGIC_SIZE	((HEADER_CHECKSUM - HEADER_MAGIC) * 4)
+#define HEADER_SIZE	0x600
+
+#define CHECKSUM_SEED	0x5F0A6C39
+
+#define BOOT0_OFFSET	8192
+#define BOOT0_SIZE	32768
+#define BOOT0_END_KB	((BOOT0_OFFSET + BOOT0_SIZE) / 1024)
+#define BOOT0_ALIGN	0x4000
+#define UBOOT_LOAD_ADDR	0x4a000000
+#define UBOOT_OFFSET_KB	19096
+
+#define ALIGN(x, a) ((((x) + (a) - 1) / (a)) * (a))
+
+static uint32_t calc_checksum(void *buffer, size_t length)
+{
+	uint32_t *buf = buffer;
+	uint32_t sum = 0;
+	size_t i;
+
+	for (i = 0; i < length / 4; i++)
+		sum += buf[i];
+
+	return sum;
+}
+
+#define CHUNK_SIZE 262144
+
+static ssize_t read_file(const char *filename, char **buffer_addr)
+{
+	FILE *fp;
+	char* buffer;
+	size_t len, ret;
+
+	fp = fopen(filename, "rb");
+	if (fp == NULL)
+		return -errno;
+
+	buffer = malloc(CHUNK_SIZE);
+	for (len = 0; !feof(fp); len += ret) {
+		ret = fread(buffer + len, 1, CHUNK_SIZE, fp);
+		if (ferror(fp)) {
+			fclose(fp);
+			free(buffer);
+			return -errno;
+		}
+
+		if (!feof(fp))
+			buffer = realloc(buffer, len + 2 * CHUNK_SIZE);
+	}
+
+	*buffer_addr = realloc(buffer, len);
+
+	fclose(fp);
+	return len;
+}
+
+#define ZBUFSIZE 1024
+static int fill_zeroes(FILE *stream, off_t size)
+{
+	static const char zeroes[ZBUFSIZE] = {};
+	int chunk, ret;
+
+	while (size) {
+		chunk = (size > ZBUFSIZE ? ZBUFSIZE : size);
+
+		ret = fwrite(zeroes, 1, chunk, stream);
+		if (!ret)
+			return -errno;
+
+		size -= ret;
+	}
+
+	return 0;
+}
+
+static int pseek(FILE *stream, long offset)
+{
+	int ret;
+
+	ret = fseek(stream, offset, SEEK_CUR);
+	if (!ret)
+		return ret;
+
+	if (ret < 0 && errno != ESPIPE)
+		return -errno;
+
+	return fill_zeroes(stream, offset);
+}
+
+#define SEC_PER_TRACK	63
+#define TRACKS_PER_CYL	255
+static void chs_encode(int lba, uint8_t *chs)
+{
+	int c, h, s;
+
+	s = (lba % SEC_PER_TRACK) + 1;
+	h = ((lba / SEC_PER_TRACK) % TRACKS_PER_CYL) & 0xff;
+	c = lba / (SEC_PER_TRACK * TRACKS_PER_CYL) & 0x3ff;
+
+	chs[0] = h;
+	chs[1] = s | (c >> 8);
+	chs[2] = c & 0xff;
+}
+
+static void create_part_table(FILE *stream, off_t fat_size, bool efi,
+			      bool patch)
+{
+	union {
+		uint8_t b[16];
+		uint32_t l[16];
+	} fatp, fwp, zero = {};
+	int i;
+
+	fat_size /= 512;
+	for (i = 0; i < 27; i++)
+		fwrite(zero.b, 16, 1, stream);
+	fwrite(zero.b, 14, 1, stream);
+
+	fatp.b[0] = 0x80;
+	fatp.l[2] = htole32((patch ? 1 : 20) * 2048);
+	fatp.l[3] = htole32(fat_size);
+	chs_encode(fatp.l[2], &fatp.b[1]);
+	fatp.b[4] = efi ? 0xef : 0x06;
+	chs_encode(fatp.l[2] + fatp.l[3] - 1, &fatp.b[5]);
+	fwrite(fatp.b, 16, 1, stream);
+
+	if (patch) {
+		fwrite(zero.b, 16, 1, stream);
+	} else {
+		fwp.b[0] = 0;
+		fwp.l[2] = htole32(1);
+		fwp.l[3] = htole32(20 * 2048 - 1);
+		chs_encode(fwp.l[2], &fwp.b[1]);
+		fwp.b[4] = 0xda;
+		chs_encode(fwp.l[2] + fwp.l[3] - 1, &fwp.b[5]);
+		fwrite(fwp.b, 16, 1, stream);
+	}
+
+	fwrite(zero.b, 16, 1, stream);
+	fwrite(zero.b, 16, 1, stream);
+
+	zero.b[0] = 0x55;
+	zero.b[1] = 0xaa;
+	fwrite(zero.b, 2, 1, stream);
+}
+
+/*
+ * Scan the boot0 binary for a Thumb2 instruction which loads a wide
+ * immediate into a register (MOVW <Rd>, #<imm16>), which is encoded as:
+ * "1111.0i10.0100.imm4|0imm3.Rd.imm8", where the imm16 is constructed as:
+ * "imm4:i:imm3:imm8". Match for an instruction which loads the "orig" value
+ * into any register and replace it with a load with the "new" value.
+ * Returns the number of patched instructions.
+ */
+static int patch_boot0(uint16_t *boot0, uint16_t orig, uint16_t new)
+{
+	int i;
+	uint16_t first = 0, imm;
+	int patched = 0;
+
+	for (i = 0; i < 16384; i++) {
+		if ((boot0[i] & 0xfbf0) == 0xf240) {
+			first = boot0[i];
+			continue;
+		}
+		if (!first)
+			continue;
+		if (boot0[i] & 0x8000) {
+			first = 0;
+			continue;
+		}
+		imm = (first & 0xf) << 12;
+		imm |= (first & 0x0400) << 1;
+		imm |= (boot0[i] & 0x7000) >> 4;
+		imm |= boot0[i] & 0x00ff;
+
+		if (imm == orig) {
+			first &= 0xfbf0;
+			first |= (new & 0xf000) >> 12;
+			first |= (new & 0x0800) >> 1;
+			boot0[i - 1] = first;
+			boot0[i] &= 0x8f00;
+			boot0[i] |= (new & 0x0700) << 4;
+			boot0[i] |= new & 0x00ff;
+
+			patched++;
+		}
+
+		first = 0;
+	}
+
+	return patched;
+}
+
+static void usage(const char *progname, FILE *stream)
+{
+	fprintf(stream, "boot0img: assemble an Allwinner boot image for boot0\n"
+		"usage: %s [-h] [-e] [-o output.img] [-b boot0.img]\n"
+		"       [-u u-boot-dtb.bin] -d bl31.bin -s scp.bin [-a addr]\n",
+			progname);
+	fprintf(stream, "       %s [-c file]\n", progname);
+	fprintf(stream, "\t-h|--help: this help output\n"
+		"\t-q|--quiet: be less verbose\n"
+		"\t-o|--output: output file name, stdout if omitted\n"
+		"\t-D|--device: output device file, -o gets ignored\n"
+		"\t-b|--boot0: boot0 image to embed into the image\n"
+		"\t-B|--boot0-patch: patch boot0 image and embed into image\n"
+		"\t-c|--checksum: calculate checksum of file\n"
+		"\t-u|--uboot: U-Boot image file (without SPL)\n"
+		"\t-F|--fdt: Device Tree file (for legacy u-boot))\n"
+		"\t-s|--sram: image file to write into SRAM\n"
+		"\t-d|--dram: image file to write into DRAM\n"
+		"\t-a|--arisc_entry: reset vector address for arisc\n"
+		"\t-e|--embedded_header: use header from U-Boot binary\n"
+		"\t-p|--partition: add a partition table with an <n> MB FAT partition\n"
+		"\t-P|--EFI-partition: as above, but as an EFI partition\n\n");
+	fprintf(stream, "Giving a boot0 image name will create an image which "
+		"can be written directly\nto an SD card. Otherwise just the "
+		"blob with the secondary firmware parts will\nbe assembled.\n");
+	fprintf(stream, "\nInstead of an actual binary for the DRAM, you can "
+		"write ARM or AArch64\ntrampoline code into that location. It "
+		"will jump to the specified address.\n");
+	fprintf(stream, "\t--dram trampoline64:<addr>\n");
+	fprintf(stream, "\t--dram trampoline32:<addr>\n");
+	fprintf(stream, "\nSpecifying an arisc entry address will populate the "
+		"arisc reset exception vector\nwith an OpenRISC instruction to "
+		"jump to that specified address.\n");
+	fprintf(stream, "The given SRAM binary will thus be written behind the "
+		"exception vector area.\n");
+	fprintf(stream, "\t--arisc_entry 0x44008\n");
+}
+
+/* Do a realloc(), but clear the new part if the new allocation is bigger. */
+static void *realloc_zero(void *ptr, ssize_t *sizeptr, ssize_t newsize)
+{
+	void *ret;
+
+	ret = realloc(ptr, newsize);
+
+	if (newsize > *sizeptr)
+		memset((char*)ret + *sizeptr, 0, newsize - *sizeptr);
+
+	*sizeptr = newsize;
+
+	return ret;
+}
+
+static int checksum_file(const char *filename, bool verbose)
+{
+	ssize_t size;
+	char *buffer;
+	uint32_t checksum, old_checksum;
+
+	size = read_file(filename, &buffer);
+	if (size < 0)
+		return size;
+
+	checksum = calc_checksum(buffer, 12);
+	old_checksum = calc_checksum(buffer + 12, 4);
+	checksum += calc_checksum(buffer + 16, size - 16);
+
+	if (verbose) {
+		fprintf(stdout, "%s: %zd Bytes\n", filename, size);
+		fprintf(stdout, "nominal checksum: 0x%08x\n",
+			checksum + old_checksum);
+	}
+	checksum += CHECKSUM_SEED;
+	fprintf(stdout, "0x%08x\n", checksum);
+	if (verbose) {
+		fprintf(stdout, "00000000  %02x %02x %02x %02x\n",
+			checksum & 0xff, (checksum >> 8) & 0xff,
+			(checksum >> 16) & 0xff, checksum >> 24);
+		fprintf(stdout, "old checksum: 0x%08x, %smatching\n",
+			old_checksum, old_checksum == checksum ? "" : "NOT ");
+	}
+
+	return old_checksum != checksum;
+}
+
+static int copy_boot0(FILE *outf, const char *boot0fname, bool patch)
+{
+	char *buffer;
+	ssize_t size;
+	int nr_patches;
+	uint32_t checksum = 0;
+
+	while (true) {			/* loop to potentially undo patching */
+		size = read_file(boot0fname, &buffer);
+		if (size < 0)
+			return size;
+
+		if (size > BOOT0_SIZE) {
+			fprintf(stderr,
+				"boot0 is bigger than 32K (%zd Bytes)\n", size);
+			return -1;
+		}
+
+		if (patch) {
+			nr_patches = patch_boot0((void *)buffer,
+						 BOOT0_END_KB * 2,
+						 BOOT0_END_KB * 2);
+			if (nr_patches == 2)		/* already patched */
+				break;
+
+			nr_patches = patch_boot0((void *)buffer,
+						 UBOOT_OFFSET_KB * 2,
+						 BOOT0_END_KB * 2);
+			if (nr_patches != 2) {		/* something's wrong */
+				patch = false;
+				continue;		/* reload file */
+			}
+			checksum = 1;
+		} else {
+			nr_patches = patch_boot0((void *)buffer,
+						 UBOOT_OFFSET_KB * 2,
+						 UBOOT_OFFSET_KB * 2);
+			if (nr_patches == 2)		/* all fine */
+				break;
+
+			nr_patches = patch_boot0((void *)buffer,
+						 BOOT0_END_KB * 2,
+						 BOOT0_END_KB * 2);
+			if (nr_patches != 2)		/* unknown boot0 */
+				break;			/* proceed unaltered */
+
+			/* patched boot0, revert to old U-Boot position */
+			nr_patches = patch_boot0((void *)buffer,
+						 BOOT0_END_KB * 2,
+						 UBOOT_OFFSET_KB * 2);
+			checksum = 1;
+			patch = false;
+		}
+		break;
+	}
+	if (checksum) {
+		checksum = calc_checksum(buffer, 12);
+		checksum += CHECKSUM_SEED;
+		checksum += calc_checksum(buffer + 16, size - 16);
+		((uint32_t *)buffer)[3] = htole32(checksum);
+	}
+
+	fwrite(buffer, size, 1, outf);
+
+	free(buffer);
+	return (int)patch;
+}
+
+int main(int argc, char **argv)
+{
+	static const struct option lopts[] = {
+		{ "help",	0, 0, 'h' },
+		{ "uboot",	1, 0, 'u' },
+		{ "fdt",	1, 0, 'F' },
+		{ "sram",	1, 0, 's' },
+		{ "dram",	1, 0, 'd' },
+		{ "checksum",	1, 0, 'c' },
+		{ "output",	1, 0, 'o' },
+		{ "boot0",	1, 0, 'b' },
+		{ "boot0-patch",	1, 0, 'B' },
+		{ "embedded_header",	0, 0, 'e' },
+		{ "arisc_entry",1, 0, 'a' },
+		{ "quiet",	0, 0, 'q' },
+		{ "partition",	1, 0, 'p' },
+		{ "efi-partition",	1, 0, 'P' },
+		{ "device",	1, 0, 'D' },
+		{ NULL, 0, 0, 0 },
+	};
+	uint32_t *header;
+	uint32_t checksum = 0;
+	off_t offset, part_size = -1;
+	const char *uboot_fname = NULL, *boot0_fname = NULL, *dram_fname = NULL;
+	const char *sram_fname = NULL, *chksum_fname = NULL, *out_fname = NULL;
+	const char *arisc_addr = NULL, *device_fname = NULL, *dtb_fname = NULL;
+	char *uboot_buf = NULL, *dtb_buf = NULL;
+	uint32_t *dram_buf = NULL, *sram_buf = NULL;
+	ssize_t uboot_size, sram_size, dram_size, dtb_size, old_uboot_size;
+	FILE *outf;
+	int ch;
+	bool quiet = false, embedded_header = false, patched_boot0 = false;
+	bool efi_part = false;
+
+	if (argc <= 1) {
+		/* with no arguments at all: default to showing usage help */
+		usage(argv[0], stdout);
+		return 0;
+	}
+
+	while ((ch = getopt_long(argc, argv, "heqo:u:F:c:b:B:s:d:a:p:P:D:",
+				 lopts, NULL)) != -1) {
+		switch(ch) {
+		case '?':
+			usage(argv[0], stderr);
+			return 1;
+		case 'h':
+			usage(argv[0], stdout);
+			return 0;
+		case 'o':
+			out_fname = optarg;
+			break;
+		case 'B':
+			patched_boot0 = true;
+			/* fall through */
+		case 'b':
+			boot0_fname = optarg;
+			break;
+		case 'u':
+			uboot_fname = optarg;
+			break;
+		case 'F':
+			dtb_fname = optarg;
+			break;
+		case 'd':
+			dram_fname = optarg;
+			break;
+		case 's':
+			sram_fname = optarg;
+			break;
+		case 'c':
+			chksum_fname = optarg;
+			break;
+		case 'q':
+			quiet = true;
+			break;
+		case 'e':
+			embedded_header = true;
+			break;
+		case 'a':
+			arisc_addr = optarg;
+			break;
+		case 'P':
+			efi_part = true;
+			/* fall through */
+		case 'p':
+			part_size = atoi(optarg);
+			break;
+		case 'D':
+			device_fname = optarg;
+			break;
+		}
+	}
+
+	if (embedded_header && !uboot_fname) {
+		fprintf(stderr, "must provide U-Boot file (-u) with embedded header (-e)\n");
+		usage(argv[0], stderr);
+		return 2;
+	}
+
+	if (chksum_fname)
+		return checksum_file(chksum_fname, !quiet);
+
+	if (!sram_fname) {
+		fprintf(stderr, "boot0 requires an \"SCP\" binary.\n");
+		usage(argv[0], stderr);
+		return 2;
+	}
+
+	if (uboot_fname) {
+		if (!quiet)
+			fprintf(stderr, "U-Boot: %s: ", uboot_fname);
+
+		uboot_size = read_file(uboot_fname, &uboot_buf);
+		if (uboot_size < 0) {
+			perror(quiet ? uboot_fname : "");
+			return 3;
+		}
+
+		if (!quiet)
+			fprintf(stderr, "%zd Bytes\n", uboot_size);
+
+		uboot_buf = realloc_zero(uboot_buf, &uboot_size,
+					 ALIGN(uboot_size, 512));
+
+		if (dtb_fname) {
+			if (!quiet)
+				fprintf(stderr, "FDT   : %s: ", dtb_fname);
+			dtb_size = read_file(dtb_fname, &dtb_buf);
+			if (dtb_size < 0) {
+				perror(quiet ? dtb_fname : "");
+				return 3;
+			}
+			if (!quiet)
+				fprintf(stderr, "%zd Bytes\n", dtb_size);
+
+			dtb_buf = realloc_zero(dtb_buf, &dtb_size,
+					 ALIGN(dtb_size, 1024));
+
+			old_uboot_size = uboot_size;
+			uboot_buf = realloc_zero(uboot_buf, &uboot_size,
+					 old_uboot_size + dtb_size);
+			memcpy(uboot_buf + old_uboot_size, dtb_buf, dtb_size);
+		}
+
+		/* Use the buffer within uboot_buf for holding the header */
+		if (embedded_header) {
+			header = (uint32_t *)uboot_buf;
+			checksum += calc_checksum(uboot_buf + HEADER_SIZE,
+						  uboot_size - HEADER_SIZE);
+			offset = uboot_size;
+		} else {
+			header = calloc(HEADER_SIZE, 1);
+			checksum += calc_checksum(uboot_buf, uboot_size);
+			offset = uboot_size + HEADER_SIZE;
+		}
+	} else {
+		offset = HEADER_SIZE;
+		header = calloc(HEADER_SIZE, 1);
+	}
+
+	/* Assuming an embedded header already has a branch instruction. */
+	if (!embedded_header) {
+		uint32_t br_ins;
+		bool jump32 = false;
+
+		br_ins = jump32 ? 0xea000000 : 0x14000000;
+		br_ins |= (jump32 ? HEADER_SIZE - 8 : HEADER_SIZE) / 4;
+		header[HEADER_JUMP_INS] = htole32(br_ins);
+	}
+
+	if (dram_fname) {
+		if (!quiet)
+			fprintf(stderr, "DRAM  : %s: ", dram_fname);
+
+		if (!strncmp(dram_fname, "trampoline64:", 13) ||
+		    !strncmp(dram_fname, "trampoline32:", 13)) {
+			bool aarch64 = dram_fname[10] == '6';
+			uint32_t address;
+			char *endptr;
+
+			address = strtoul(dram_fname + 13, &endptr, 0);
+
+			dram_buf = calloc(512, 1);
+			dram_size = 512;
+			if (aarch64) {
+					/* ldr	x16, 0x8 */
+				dram_buf[0] = htole32(0x58000050);
+					/* br	x16 */
+				dram_buf[1] = htole32(0xd61f0200);
+				dram_buf[2] = htole32(address);
+					/* high word is always 0 */
+				dram_buf[3] = 0;
+			} else {
+					/* ldr	r12, [pc, #-0] */
+				dram_buf[0] = htole32(0xe51fc000);
+					/* bx	r12 */
+				dram_buf[1] = htole32(0xe12fff1c);
+				dram_buf[2] = htole32(address);
+			}
+			if (!quiet)
+				fprintf(stderr, "\n");
+		} else {
+			dram_size = read_file(dram_fname, (char**)&dram_buf);
+			if (dram_size < 0) {
+				perror(quiet ? dram_fname : "");
+				return 3;
+			}
+
+			if (!quiet)
+				fprintf(stderr, "%zd Bytes\n", dram_size);
+		}
+
+		dram_buf = realloc_zero(dram_buf, &dram_size,
+					ALIGN(dram_size, 512));
+		checksum += calc_checksum(dram_buf, dram_size);
+
+		header[HEADER_SECS + 0] = htole32(offset);
+		header[HEADER_SECS + 1] = htole32(dram_size);
+
+		offset += dram_size;
+	}
+
+	if (!quiet)
+		fprintf(stderr, "SRAM  : %s: ", sram_fname);
+
+	sram_size = read_file(sram_fname, (char **)&sram_buf);
+	if (sram_size < 0) {
+		perror(quiet ? sram_fname : "");
+		return 3;
+	}
+
+	if (!quiet)
+		fprintf(stderr, "%zd Bytes\n", sram_size);
+
+	if (arisc_addr)
+		sram_size += 0x4000;
+
+	sram_buf = realloc_zero(sram_buf, &sram_size,
+				ALIGN(sram_size, 512));
+
+	/*
+	 * Move the loaded code to the SRAM part behind the OpenRISC
+	 * exception vector part, which is in fact only sparsely
+	 * implemented on the Allwinner SoCs.
+	 * Add an OpenRISC jump instruction into the arisc entry point.
+	 */
+	if (arisc_addr) {
+		uint32_t address;
+		char *endptr;
+
+		address = strtoul(arisc_addr, &endptr, 0);
+		memmove(sram_buf + 0x1000, sram_buf, sram_size - 0x4000);
+		memset(sram_buf, 0, 0x4000);
+			/* OpenRISC: l.j <offset> */
+		sram_buf[64] = htole32((address - 0x40100) / 4);
+			/* OpenRISC: l.nop (delay slot) */
+		sram_buf[65] = htole32(0x15000000);
+	}
+	checksum += calc_checksum(sram_buf, sram_size);
+
+	header[HEADER_SECS + 8] = htole32(offset);
+	header[HEADER_SECS + 9] = htole32(sram_size);
+
+	offset += sram_size;
+
+	/* fill the static part of the header */
+	strncpy((char*)&header[HEADER_MAGIC], "uboot", MAGIC_SIZE);
+	header[HEADER_CHECKSUM] = CHECKSUM_SEED;
+	header[HEADER_ALIGN] = htole32(BOOT0_ALIGN);
+	header[HEADER_LOADADDR] = htole32(UBOOT_LOAD_ADDR);
+	header[HEADER_PRIMSIZE] = htole32(offset);
+
+	if (dtb_fname)
+		header[HEADER_DTOFFSET] = htole32(old_uboot_size);
+
+	offset = ALIGN(offset, BOOT0_ALIGN);
+	header[HEADER_LENGTH] = htole32(offset);
+	offset -= le32toh(header[HEADER_PRIMSIZE]);
+
+	checksum += calc_checksum(header, HEADER_SIZE);
+	header[HEADER_CHECKSUM] = htole32(checksum);
+
+	if (device_fname) {
+		outf = fopen(device_fname, "r+b");
+		if (!outf) {
+			perror(device_fname);
+			return 2;
+		}
+		out_fname = NULL;
+	} else {
+		if (out_fname)
+			outf = fopen(out_fname, "wb");
+		else
+			outf = stdout;
+	}
+
+	if (outf == NULL) {
+		perror(out_fname);
+		return 5;
+	}
+
+	if (part_size != -1)
+		create_part_table(outf, part_size * 1024 * 1024, efi_part,
+				  patched_boot0);
+	else if (device_fname)
+		pseek(outf, 512);
+
+	if (boot0_fname) {
+		int ret;
+
+		if (device_fname || part_size != -1)
+			pseek(outf, BOOT0_OFFSET - 512);
+
+		ret = copy_boot0(outf, boot0_fname, patched_boot0);
+		if (ret < 0)
+			perror(boot0_fname);
+		else
+			patched_boot0 = ret;
+
+		if (!patched_boot0)
+			pseek(outf, (UBOOT_OFFSET_KB - BOOT0_END_KB) * 1024);
+	} else {
+		if (device_fname || part_size != -1)
+			pseek(outf, UBOOT_OFFSET_KB * 1024 - 512);
+	}
+
+	if (!embedded_header)
+		fwrite(header, HEADER_SIZE, 1, outf);
+
+	if (uboot_fname)
+		fwrite(uboot_buf, uboot_size, 1, outf);
+	if (dram_fname)
+		fwrite(dram_buf, dram_size, 1, outf);
+	fwrite(sram_buf, sram_size, 1, outf);
+
+	if (device_fname) {
+		fill_zeroes(outf, offset);
+	} else {
+		long fpos;
+
+		pseek(outf, offset);
+
+		fpos = ftell(outf);
+		if (fpos >= 0 && ftruncate(fileno(outf), fpos))
+			perror("error truncating output file");
+	}
+
+	fclose(outf);
+
+	free(uboot_buf);
+	free(dtb_buf);
+	free(sram_buf);
+	free(dram_buf);
+
+	if (!embedded_header)
+		free(header);
+
+	return 0;
+}
